AsyncTask原理剖析

基本用法

AsyncTask是Android种常用的一种轻量级异步类,它的基本使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class SomeTask extends AsyncTask<Integer, Integer, String>{  
@Override
protected void onPreExecute() {
super.onPreExecute();
//任务执行前的回调
}
@Override
protected String doInBackground(Integer... params) {
//这里我们做耗时的操作
}
@Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
//处理任务进度
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
//回调,这里可以进行UI的操作
}
}

SomeTask task = new SomeTask();
task.execute(100);

AsycTask种有四个基本的回调方法,它们分别是

  1. onPreExecute 在任务执行前在UI线程回调
  2. doInBackground 在后台线程种执行任务
  3. onProgressUpdate 将任务的执行进度通过onProgreeUpdate通知给UI线程
  4. onPostExecute 将任务执行的结果回调给UI线程

源码分析

为了清楚的了解AsyncTask内部的原理机制,本篇将会对其源码进行解析,在这之前我们先看看它的类结构定义

AsyncTask的类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();//CPU数目
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;//初始的线程个数=当前CPU数目+1
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;//最大的线程个数=CPU数目*2+1
private static final int KEEP_ALIVE = 1;//保活时间1s

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);

public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};

private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);//阻塞队列的大小为128

/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
//根据上面的配置我们可以计算出 可以提交的总任务数目=128+CPU数目*2+1
/**
* An {@link Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;

private static final InternalHandler sHandler = new InternalHandler();

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;

private volatile Status mStatus = Status.PENDING;//任务的状态

private final AtomicBoolean mCancelled = new AtomicBoolean();
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

}

AsyncTask是个抽象的泛型类,它有三个抽象的参数<Params, Progress, Result>,分别代表着后台任务附带的参数Params,执行任务的进度Progress以及执行执行的结果Result。它的类成员主要如下:

  1. THREAD_POOL_EXECUTOR 这是AsyncTask执行异步任务的线程池,线程池的基本大小为CPU_COUNT + 1,最大大小为CPU_COUNT * 2 + 1,并使用一个有界大小为128的LinkedBlockingQueue作为其工作队列,线程保活时间为1s。
  2. sDefaultExecutor 它是一个SerialExecutor,这个是用来对提交的任务进行管理的的执行器,任务最终通过它提交给线程池,后面再详细介绍。
  3. sHandler 它是一个InternalHandler,负责投递执行进度和最终结果给UI线程的Handler。
  4. mWorker 这是一个WorkerRunnable类型的Callable,负责执行AsyncTask的后台任务。
  5. mFuture FutureTask可以看作是mWorker的包裹,负责处理一些任务执行完的收尾工作,它是最终提交给SerialExecutor的任务。
  6. mStatus AsyncTask当前的状态,默认是Status.PENDING状态表示等待任务中。

需要注意的是除了mStatus,以上所有的成员均为static final类型,也就是说这些成员是给AsyncTask类使用的。

了解了类成员再看看它的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public AsyncTask() {
//mWorker就是我们要执行的后台任务,它是一个Callable
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);//设置我们的回调被调用了

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));//这里调用了我们的后台任务,并在执行完成后post到UI线程
}
};
//通过我们的后台任务mWorker来创建一个FutureTask,当任务执行完成后调用done
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
//当我们的任务可能由于异常等原因没有得到调用后这时候依然需要调用postResult将结果投递到UI线程
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}

AsyncTask只有这一个默认构造方法,在其内部会构造一个WorkerRunnable代表着我们的后台任务,WorkerRunnable仅仅是继承了Callable同时内部有一个Params数组代表着后台任务附带的参数,以及一个最终提交给任务调度器的FutureTask,它实际上可以看作是mWorker的包裹,内部保存了mWroker这个callable。

任务提交

AsyncTask一般通过execute方法来提交我们的异步任务,在提交时会传递参数Params给execute方法,我们看看它的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}

mStatus = Status.RUNNING;//置任务的状态为RUNNINT

onPreExecute();//回调任务执行前的回调

mWorker.mParams = params;
exec.execute(mFuture);//执行mFuture的run方法

return this;
}

在execute方法中通过executeOnExecutor实现,在它内部首先检测当前mStatus的状态,如果已经执行或者完成那么抛出异常,这说明AsyncTask是只能够被执行一次的,否则置mStatus为Status.RUNNING表示正在执行状态。然后回调onPreExecute,这个是任务执行前的回调。随后将参数保存在mWorker中,然后通过将后台任务提交给sDefaultExecutor即任务调度器它是个SerialExecutor,我们看看它的实现。

任务调度器SerialExecutor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();//任务队列
Runnable mActive;//当前执行的任务

public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();//当一个任务执行完毕就接着执行下一个
}
}
});//任务提交后先添加到任务队列中
if (mActive == null) {//如果没有任务执行
scheduleNext();//从任务队列中取出任务开始执行
}
}

protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}

SerialExecutor内部实现很简单,它有一个ArrayDeque类型的任务队列mTasks,以及一个Runnable类型的mActive代表当前执行的任务。通过execute方法将mFuture包装后添加到mTasks中,然后判断mActivie是否为null,一开始肯定是null,这时候就通过scheduleNext从mTask队列中取出一个任务赋值给mActive然后交给线程池执行。线程池会执行下面的代码

1
2
3
4
5
6
7
public void run() {
try {
r.run();
} finally {
scheduleNext();//当一个任务执行完毕就接着执行下一个
}
}

这段代码将在线程中执行,这里的r实际就是我们提交的mFuture,这时候执行它的run方法,执行完成后调用scheduleNext方法开始下一个任务的执行。可见SerialExecutor是一个串行的任务调度器。它在一个任务执行完成后才开始调用下个任务执行。

任务的执行过程

在上面我们知道了SerialExecutor通过scheduleNext从任务队列中取出一个任务提交给线程池开始执行,而提交的任务内部最终会去执行FutureTask的run方法,而run方法中最终会执行它内部的callable,也就是mWorker,还记得么,我们在构造方法中创建的它,实现是这样的:

1
2
3
4
5
6
7
8
9
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);//设置我们的回调被调用了

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));//这里调用了我们的后台任务,并在执行完成后post到UI线程
}
};

在call回调中执行doInBackground方法执行后台任务,并将执行的结果通过postResult发送给UI线程,这里都是在线程池中的线程中执行的。

结果的投递

任务执行的结果是在线程中通过postResult方法投递给UI线程的,我们看看它的实现:

1
2
3
4
5
6
7
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

通过sHandler处理投递的MESSAG_POST_RESULT消息,投递的结果被封装在了AsyncTaskResult类型的对象中。我们看下这个类

1
2
3
4
5
6
7
8
9
10
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;

AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}

在AsyncTaskResult内部保存了AsyncTask的实例mTask以及投递的结果mData。

接下来就是通过sHandler也就是InternalHandler来处理消息,我们看看InternalHandler的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static class InternalHandler extends Handler {
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT://执行的结果通过InternalHandler来执行
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}

在InternalHandler内部只处理了两类消息,MESSAGE_POST_RESULT和MESSAGE_POST_PROGRESS,当我们在doBackground方法中执行后台任务时,可以通过publishProgress方法来通知UI线程我们当前任务的进度,在publishProgress方法中会通过InternalHandler来发送MESSAGE_POST_PROGRESS消息,InternalHandler接收到消息后通过onProgressUpdate回调告知UI层,而MESSAGE_POST_RESULT通过finish来处理。我们先看看finish方法的具体实现

1
2
3
4
5
6
7
8
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);//调用任务完成的回调,这里已经在UI线程了
}
mStatus = Status.FINISHED;//标记任务已经完成了
}

在finish方法中首先判断任务是否被取消,如果已经取消回调onCancelled处理,否则执行onPostExecute回调将结果投递给UI线程,最后修改mStatus的状态为Status.FINISHED表示任务已经执行完成。

总结

从AsyncTask的实现来看,它本质上和Thread+Handler的方式类似,内部对于任务的处理默认是串行进行处理的(当然如果通过executeOnExecutor也是可以进行并行处理的)。AsyncTask最大的又是也许就是封装的几个线程回调使得在Android执行后台任务时使用起来更加方便整体来看是一个完整可控的过程。但由于AsyncTask的设计涉及到UI线程,所以太耗时的任务并不太适用于AsyncTask,况且在串行的情况下提交的任务可能要经过很长时间才能被处理完成。它内部的线程池决定了它适合同类型的任务,而对不同类型的任务处理第一可能会降低线程池的效率,第二可能会增加代码复杂性。另一个方面AsyncTask的使用不当会带来令人诟病的内存泄漏问题,因为AsyncTask和UI组件的生命周期并不同步,比如非静态的AsyncTask会持有Activity的引用,当屏幕旋转时AsyncTask可能会去更新一个已经销毁掉的Activity,这些都要求我们在使用AsyncTask时应该更加谨慎,一般是采用静态的AsyncTask结合弱引用的方式,并且在UI组件生命期结束的时候通过cancel及时的取消任务,同时在AsyncTask内部我们也可以根据取消状态来做相应的处理。

坚持原创技术分享,您的支持将鼓励我继续创作!